package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointInBounds;
import static xj.graph2d.DrawShapeConstants.PointOnPolyCurveCtrPoint;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.anchorColor;
import static xj.graph2d.DrawShapeConstants.ctrStroke;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.graph2d.DrawShapeConstants.frameMarkFilled;
import static xj.graph2d.DrawShapeConstants.markStroke;
import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.util.DrawingUtilities;
import xj.lang.CodeWriter;
import xj.util.gui.Point2DFloat;

public class PolyCurveShape 
  extends PolyLineShape {

  public PolyCurveShape() {
    this(false);
  }

  public PolyCurveShape(float x1, float y1) {
    this(x1, y1, false);
  }

  public PolyCurveShape(boolean createComp) {
    super(createComp);
    ctr1 = new Point2DFloat();
    ctr2 = new Point2DFloat();
  }

  public PolyCurveShape(float x1, float y1, boolean createComp) {
    super(x1, y1, createComp);
    ctr1 = new Point2DFloat();
    ctr2 = new Point2DFloat();
  }

  @Override
  public DrawShape makeInstance() {
    return new PolyCurveShape(comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    if (s != null) {
      super.copyTo(s);
      if (s instanceof PolyCurveShape) {
	PolyCurveShape curve = (PolyCurveShape) s;
	if (ctr1 != null) {
	  curve.setCtr1((float) ctr1.getX(), (float) ctr1.getY());
	}
	if (ctr2 != null) {
	  curve.setCtr2((float) ctr2.getX(), (float) ctr2.getY());
	}
      }
    }
  }

  @Override
  public String getName() {
    return "Polycurve";
  }

  public Point2D getCtr1() {
    return ctr1;
  }

  public Point2D getCtr2() {
    return ctr2;
  }

  public void setCtr1(float x, float y) {
    ctr1.setLocation(x, y);
  }

  public void setCtr2(float x, float y) {
    ctr2.setLocation(x, y);
  }

  public void moveCtr1(float dx, float dy) {
    ctr1.setLocation(ctr1.getX() + dx, ctr1.getY() + dy);
    // curve = null;
    calculateBounds();
  }

  public void moveCtr2(float dx, float dy) {
    ctr2.setLocation(ctr2.getX() + dx, ctr2.getY() + dy);
    // curve = null;
    calculateBounds();
  }

  @Override
  public void move(float dx, float dy) {
    ctr1.setLocation(ctr1.getX() + dx, ctr1.getY() + dy);
    ctr2.setLocation(ctr2.getX() + dx, ctr2.getY() + dy);
    super.move(dx, dy);
  }

  @Override
  public void adjustControlPoints() {
    int n = getNumberOfPoints();
    float x = getX();
    float y = getY();
    Point2D p = getPoint(0);
    float x1 = x + (float) p.getX();
    float y1 = y + (float) p.getY();
    float cx1 = (x1 + x) / 2 + (x1 - x) / 4;
    float cy1 = (y1 + y) / 2 - (y1 - y) / 4;
    if (n > 1) {
      if (n > 2) {
	p = getPoint(n - 3);
	x1 = x + (float) p.getX();
	y1 = y + (float) p.getY();
      } else {
	x1 = x;
	y1 = y;
      }
    }
    p = getLast();
    float x2 = x + (float) p.getX();
    float y2 = y + (float) p.getY();
    float cx2 = (x1 + x2) / 2 - (x2 - x1) / 4;
    float cy2 = (y1 + y2) / 2 + (y2 - y1) / 4;
    ctr1.setLocation(cx1, cy1);
    ctr2.setLocation(cx2, cy2);
  }

  @Override
  public void adjustEnds(GraphNodeBase src, 
			 GraphNodeBase des, 
			 boolean adjustCtrPts) {

    if (debug) System.out.println("PolyCurveShape.adjustEnds()");

    Point2D q1 = null, q2 = null;
    float x = adapter.getX();
    float y = adapter.getY();
    int n = adapter.getNumberOfPoints();

    float x1, y1, x2, y2;
    if (src != null) {
      q1 = src.intersect(ctr1, 
			 tailOffset.dx * src.getShapeWidth(),
			 tailOffset.dy * src.getShapeHeight());
    }
    if (q1 != null) {
      x1 = (float) q1.getX();
      y1 = (float) q1.getY();
    } else {
      x1 = x;
      y1 = y;
    }

    if (des != null) {
      q2 = des.intersect(ctr2, 
			 headOffset.dx * des.getShapeWidth(),
			 headOffset.dy * des.getShapeHeight());
    }
    if (q2 != null) {
      x2 = (float) q2.getX();
      y2 = (float) q2.getY();
    } else {
      Point2D last = adapter.getLastPoint();
      x2 = (float) last.getX();
      y2 = (float) last.getY();
    }


    if (src == des) { 
      adapter.setFirstOnly(x1, y1);
      adapter.setLast(x2, y2);
    } else { 
      if (adjustCtrPts) { 
	adapter.scaleFirstLast(x1, y1, x2, y2, true);
	adjustControlPoints();
      } else { 
	adapter.setFirstOnly(x1, y1);
	adapter.setLast(x2, y2);
      }
    }

    /*    
    adapter.scaleFirstLast(x1, y1, x2, y2, true);
    if (adjustCtrPts) { 
      adjustControlPoints();
    }
    */

    adapter.invalidateBounds();
    calculateBounds();
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    Point2D[] epts = new Point2D[2];
    epts[0] = ctr1;
    epts[1] = ctr2;
    adapter.setEndsAdj(x1, y1, x2, y2, epts);
    calculateBounds();
  }

  @Override
  public void scaleFirstLast(float x1, float y1, float x2, float y2,
			     boolean limit) {
    Point2D[] epts = new Point2D[2];
    epts[0] = ctr1;
    epts[1] = ctr2;
    adapter.scaleFirstLast(x1, y1, x2, y2, epts, limit);
  }

  @Override
  public void moveVertex(int i, float dx, float dy) {
    if (i >= 0) {
      int n = adapter.getNumberOfPoints();
      if (i == 0) {
	ctr1.setLocation(ctr1.getX() + dx, ctr1.getY() + dy);
	// ctr2.setLocation(ctr2.getX() + dx, ctr2.getY() + dy);
      } else if (i == (n - 1)) {
	ctr2.setLocation(ctr2.getX() + dx, ctr2.getY() + dy);
      }
      super.moveVertex(i, dx, dy);
    } else if (i == -1) {
      ctr1.setLocation(ctr1.getX() + dx, ctr1.getY() + dy);
      calculateBounds();
    } else if (i == -2) {
      ctr2.setLocation(ctr2.getX() + dx, ctr2.getY() + dy);
      calculateBounds();
    }
  }

  @Override
  public void drawFrame(Graphics2D g2) {
    adapter.drawFrame(g2, true, comp != null);

    int penSize = 1;
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      penSize = attr.getPenSize();
    }
    int marksize = DrawingUtilities.getMarkSize(penSize);
    float md = marksize / 2.0f;
    float x1 = adapter.getX();
    float y1 = adapter.getY();
    float cx1 = (float) ctr1.getX();
    float cy1 = (float) ctr1.getY();
    float cx2 = (float) ctr2.getX();
    float cy2 = (float) ctr2.getY();
    Point2D p2 = adapter.getLast();
    float x2 = (float) p2.getX() + x1;
    float y2 = (float) p2.getY() + y1;
    Color saveColor = g2.getColor();
    Stroke saveStroke = g2.getStroke();

    g2.setColor(anchorColor);
    g2.setStroke(markStroke);
    Ellipse2D e = new Ellipse2D.Float(cx1 - md, cy1 - md, marksize, marksize);
    if (frameMarkFilled) { 
      g2.fill(e);
    } else { 
      g2.draw(e);
    }
    e = new Ellipse2D.Float(cx2 - md, cy2 - md, marksize, marksize);
    if (frameMarkFilled) { 
      g2.fill(e);
    } else { 
      g2.draw(e);
    }

    g2.setStroke(ctrStroke);
    g2.draw(new Line2D.Float(cx1, cy1, x1, y1));
    g2.draw(new Line2D.Float(cx2, cy2, x2, y2));

    g2.setColor(saveColor);
    g2.setStroke(saveStroke);
  }

  @Override
  public void draw(Graphics2D g2) {
    makePolyCurve(g2);
  }

  public Shape makePolyCurve(Graphics2D g2) {
    if (head != null || tail != null) {
      setArrowPosition();
    }
    float x1, y1, x2, y2;
    Point2D p;
    int n = adapter.getNumberOfPoints() - 1;
    float x = adapter.getX();
    float y = adapter.getY();
    x1 = x;
    y1 = y;
    float cx1 = (float) ctr1.getX();
    float cy1 = (float) ctr1.getY();
    float cx2 = (float) ctr2.getX();
    float cy2 = (float) ctr2.getY();
    if (n == 1) {
      p = adapter.getPoint(0);
      x2 = (float) p.getX() + x1;
      y2 = (float) p.getY() + y1;
      double d = Point2D.distance(x1, y1, x2, y2);
      if (head != null &&  
	  head.getWidth() < d / 2) {
	if (g2 != null) { 
	  head.draw(g2);
	}
	Point2D ph = head.getTailPosition();
	x2 = (float) ph.getX();
	y2 = (float) ph.getY();
      }
      if (tail != null && 
	  tail.getWidth() < d / 2) {
	if (g2 != null) { 
	  tail.draw(g2);
	}
	Point2D pt = tail.getTailPosition();
	x1 = (float) pt.getX();
	y1 = (float) pt.getY();
      }
      Shape curve = new CubicCurve2D.Float(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
      if (g2 != null) { 
	g2.draw(curve);
      }
      return curve;
    } else if (n > 1) {
      float prevx = x;
      float prevy = y;
      float curx, cury;
      float ccx1, ccy1, ccx2, ccy2;
      float t;
      float[] tt = calculateTangent();
      if (tt != null) {
	if (debug) {
	  System.out.print("PolyCurveShape.draw() tt=");
	  if (tt != null) {
	    for (int j = 0; j < tt.length; j++) {
	      if (j > 0) {
		System.out.print(", ");
	      }
	      System.out.print(tt[j]);
	    }
	  } else {
	    System.out.print("null");
	  }
	  System.out.println();
	}

	GeneralPath polycurve = new GeneralPath(Path2D.WIND_EVEN_ODD, n);
	polycurve.moveTo(x, y);
	for (int i = 0; i < n; i++) {
	  p = adapter.getPoint(i);
	  if (p != null) {
	    curx = (float) p.getX() + x;
	    cury = (float) p.getY() + y;
	    x1 = prevx;
	    y1 = prevy;
	    x2 = curx;
	    y2 = cury;
	    if (i == 0) {
	      ccx1 = cx1;
	      ccy1 = cy1;
	      if (tail != null && 
		  tail.getWidth() < Point2D.distance(x1, y1, x2, y2)) {
		if (g2 != null) { 
		  tail.draw(g2);
		}
		Point2D p1 = tail.getTailPosition();
		x1 = (float) p1.getX();
		y1 = (float) p1.getY();
		polycurve.moveTo(x1, y1);
	      }
	    } else {
	      t = tt[i - 1];
	      float dd = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2)) / 2;
	      if (dd < 1) { 
		dd = 1; 
	      }
	      ccx1 = (float) Math.cos(t) * dd + x1;
	      ccy1 = (float) Math.sin(t) * dd + y1;
	    }
	    if (i == (n - 1)) {
	      ccx2 = cx2;
	      ccy2 = cy2;
	      if (head != null &&
		  head.getWidth() < Point2D.distance(x1, y1, x2, y2)) {
		if (g2 != null) { 
		  head.draw(g2);
		}
		Point2D p2 = head.getTailPosition();
		x2 = (float) p2.getX();
		y2 = (float) p2.getY();
	      }
	    } else {
	      t = tt[i];
	      float dd = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2)) / 2;
	      if (dd < 1) { 
		dd = 1; 
	      }
	      ccx2 = -(float) Math.cos(t) * dd + x2;
	      ccy2 = -(float) Math.sin(t) * dd + y2;
	    }
	    polycurve.curveTo(ccx1, ccy1, ccx2, ccy2, x2, y2);

	    if (debug) {
	      System.out.println("PolyCurveShape.draw() curveTo=(" + 
				 x1 + "," + x2 + ") (" + 
				 ccx1 + "," + ccy1 + ") (" + 
				 ccx2 + "," + ccy2 + ") (" + 
				 x2 + "," + y2 + ")");
	      int marksize = 2 * (int) d;
	      float md = marksize / 2.0f;
	      Color saveColor = g2.getColor();
	      g2.setColor(anchorColor);
	      g2.fill(new Rectangle2D.Float(ccx1 - md, ccy1 - md, marksize, marksize));
	      g2.fill(new Rectangle2D.Float(ccx2 - md, ccy2 - md, marksize, marksize));
	      Stroke s = g2.getStroke();
	      g2.setStroke(ctrStroke);
	      g2.draw(new Line2D.Float(ccx1, ccy1, x1, y1));
	      g2.draw(new Line2D.Float(ccx2, ccy2, x2, y2));
	      g2.setStroke(s);
	      g2.setColor(saveColor);
	    }

	    prevx = curx;
	    prevy = cury;
	  }
	}
	if (g2 != null) { 
	  g2.draw(polycurve);
	}
	return polycurve;
      }
    }
    return null;
  }

  @Override
  public Shape getOutlineShape() {
    return makePolyCurve(null);
  }

  @Override
  public int isInside(float px, float py) {
    int pos = adapter.isInside(px, py);
    if (pos == PointOutside || pos == PointInBounds) {
      if (isNearPoint((float) ctr1.getX(), (float) ctr1.getY(), px, py)) {
	pos = PointOnPolyCurveCtrPoint + 1;
      } else if (isNearPoint((float) ctr2.getX(), (float) ctr2.getY(), px, py)) {
	pos = PointOnPolyCurveCtrPoint + 2;
      }
    }
    return pos;
  }

  public void copyPointsTo(PolyShape s) {
    super.copyPointsTo(s);
    if (s instanceof PolyCurveShape) { 
      PolyCurveShape curve = (PolyCurveShape) s;
      curve.setCtr1((float) ctr1.getX(), (float) ctr1.getY());
      curve.setCtr2((float) ctr2.getX(), (float) ctr2.getY());
    }
  }

  @Override
  public void calculateBounds() {
    makeCurve();
    if (curve != null) {
      bounds.setRect(curve.getBounds());
      float x = (float) bounds.getX();
      float y = (float) bounds.getY();
      float w = (float) bounds.getWidth();
      float h = (float) bounds.getHeight();
      p1.setLocation(x, y);
      p2.setLocation(x + w, y + h);

      if (subshapes != null && subshapes.size() > 0) {
	Rectangle2D sub = getSubshapeBounds();
	if (sub != null) {
	  Rectangle2D.union(bounds, sub, bounds);
	}
      }

      framebounds.setRect(bounds.getX() - d, bounds.getY() - d, 
			  bounds.getWidth() + 2 * d, 
			  bounds.getHeight() + 2 * d);
      adjustBounds();
    }
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      String nodename;
      if (ctr1 != null) {
	nodename = getPrefixedName("CurveCtr1", namespacePrefix);
	out.println("<" + nodename + ' ' + 
		    getXMLAttributeString("x", ctr1.getX()) + ' ' + 
		    getXMLAttributeString("y", ctr1.getY()) + "/>");
      }

      if (ctr2 != null) {
	nodename = getPrefixedName("CurveCtr2", namespacePrefix);
	out.println("<" + nodename + ' '  + 
		    getXMLAttributeString("x", ctr2.getX()) + ' ' + 
		    getXMLAttributeString("y", ctr2.getY()) + "/>");
      }
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String cname = e.getLocalName();
      if ("CurveCtr1".equals(cname) || "CurveCtr2".equals(cname)) {
	String xs, ys;
	float x, y;
	xs = e.getAttribute("x");
	ys = e.getAttribute("y");
	try {
	  x = Float.parseFloat(xs);
	  y = Float.parseFloat(ys);
	  if ("CurveCtr1".equals(cname)) {
	    ctr1.setLocation(x, y);
	  } else {
	    ctr2.setLocation(x, y);
	  }
	} catch (NumberFormatException ex) {
	}
	curve = null;
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  /*
   * public void writeXML(CodeWriter out, int mode, String
   * namespacePrefix) { if (out != null) { String polyname =
   * getPrefixedName("PolyCurveShape", namespacePrefix); out.indentInc();
   * out.println("<" + polyname + " x=\"" + adapter.getX() + "\" y=\"" +
   * adapter.getY() + "\">"); writeXMLBase(out, mode, namespacePrefix);
   * writeXMLPoints(out, adapter.getPoints(), namespacePrefix); String
   * nodename = getPrefixedName("CurveCtr1", namespacePrefix); if (ctr1 !=
   * null) { out.println("<" + nodename + " x=\"" + ctr1.getX() + "\"
   * y=\"" + ctr1.getY() + "\"/>"); } else { out.println("<" + nodename + "
   * x=\"0\" y=\"0\"/>"); } nodename = getPrefixedName("CurveCtr2",
   * namespacePrefix); if (ctr2 != null) { out.println("<" + nodename + "
   * x=\"" + ctr2.getX() + "\" y=\"" + ctr2.getY() + "\"/>"); } else {
   * out.println("<" + nodename + " x=\"0\" y=\"0\"/>"); }
   * out.thisIndentDec(); out.println("</" + polyname + ">"); } }
   * 
   * public void setAttrFromXML(org.w3c.dom.Element node, String
   * namespaceURI) { if (node != null) { String uri =
   * node.getNamespaceURI(); if (namespaceURI == null ||
   * namespaceURI.equals(uri)) { super.setAttrFromXML(node, namespaceURI);
   * 
   * org.w3c.dom.NodeList nodes = node.getChildNodes(); if (nodes != null) {
   * int n = nodes.getLength(); for (int i = 0; i < n; i++) {
   * org.w3c.dom.Node cnode = nodes.item(i); if (cnode.getNodeType() ==
   * org.w3c.dom.Node.ELEMENT_NODE) { org.w3c.dom.Element e =
   * (org.w3c.dom.Element) cnode; String cname = e.getLocalName(); String
   * xs, ys; float x, y; if ("CurveCtr1".equals(cname)) { xs =
   * e.getAttribute("x"); ys = e.getAttribute("y"); try { x =
   * Float.parseFloat(xs); y = Float.parseFloat(ys); ctr1.setLocation(x,
   * y); } catch (NumberFormatException ex) {} } else if
   * ("CurveCtr2".equals(cname)) { xs = e.getAttribute("x"); ys =
   * e.getAttribute("y"); try { x = Float.parseFloat(xs); y =
   * Float.parseFloat(ys); ctr2.setLocation(x, y); } catch
   * (NumberFormatException ex) {} } } } } } } //calculateBounds(); }
   */

  protected float[] calculateTangent() {
    int n = adapter.getNumberOfPoints() - 1;
    if (n > 0) {
      float t[] = new float[n - 1];
      float xp[] = new float[n + 1];
      float yp[] = new float[n + 1];
      xp[0] = adapter.getX();
      yp[0] = adapter.getY();
      int i;
      for (i = 0; i < n; i++) {
	Point2D p = adapter.getPoint(i);
	if (p != null) {
	  xp[i + 1] = (float) p.getX() + xp[0];
	  yp[i + 1] = (float) p.getY() + yp[0];
	}
      }
      for (i = 1; i < n; i++) {
	t[i - 1] = (float) DrawingUtilities.calculateRotationAngle(xp[i - 1],
	    yp[i - 1], xp[i + 1], yp[i + 1]);
      }
      return t;
    }
    return null;
  }

  @Override
  public void setArrowPosition() {
    int n = adapter.getNumberOfPoints() - 1;
    if (n >= 1) {
      float x1 = adapter.getX();
      float y1 = adapter.getY();
      float cx1 = (float) ctr1.getX();
      float cy1 = (float) ctr1.getY();
      Point2D p = adapter.getLast();
      float x2 = (float) p.getX() + x1;
      float y2 = (float) p.getY() + y1;
      float cx2 = (float) ctr2.getX();
      float cy2 = (float) ctr2.getY();
      if (head != null) {
	head.setPosition(x2, y2);
	head.setAngle((float) DrawingUtilities.calculateRotationAngle(x2, y2,
	    cx2, cy2));
      }
      if (tail != null) {
	tail.setPosition(x1, y1);
	tail.setAngle((float) DrawingUtilities.calculateRotationAngle(x1, y1,
	    cx1, cy1));
      }
    }
  }

  protected void makeCurve() {
    float x1, y1, x2, y2;
    Point2D p;
    int n = adapter.getNumberOfPoints() - 1;

    if (n > 0 && ctr1 != null && ctr2 != null) {
      float x = adapter.getX();
      float y = adapter.getY();
      x1 = x;
      y1 = y;
      float cx1 = (float) ctr1.getX();
      float cy1 = (float) ctr1.getY();
      float cx2 = (float) ctr2.getX();
      float cy2 = (float) ctr2.getY();
      if (n == 1) {
	p = adapter.getPoint(0);
	x2 = (float) p.getX() + x1;
	y2 = (float) p.getY() + y1;
	curve = new CubicCurve2D.Float(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
      } else if (n > 1) {
	float prevx = x;
	float prevy = y;
	float curx, cury;
	float ccx1, ccy1, ccx2, ccy2;
	float t;
	float[] tt = calculateTangent();
	GeneralPath polycurve = new GeneralPath(Path2D.WIND_EVEN_ODD, n);
	polycurve.moveTo(x, y);
	for (int i = 0; i < n; i++) {
	  p = adapter.getPoint(i);
	  if (p != null) {
	    curx = (float) p.getX() + x;
	    cury = (float) p.getY() + y;
	    x1 = prevx;
	    y1 = prevy;
	    x2 = curx;
	    y2 = cury;
	    if (i == 0) {
	      ccx1 = cx1;
	      ccy1 = cy1;
	    } else {
	      t = tt[i - 1];
	      float dd = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2)) / 2;
	      if (dd < 1) { 
		dd = 1; 
	      }
	      ccx1 = (float) Math.cos(t) * dd + x1;
	      ccy1 = (float) Math.sin(t) * dd + y1;
	    }
	    if (i == (n - 1)) {
	      ccx2 = cx2;
	      ccy2 = cy2;
	    } else {
	      t = tt[i];
	      float dd = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2)) / 2;
	      if (dd < 1) { 
		dd = 1; 
	      }
	      ccx2 = -(float) Math.cos(t) * dd + x2;
	      ccy2 = -(float) Math.sin(t) * dd + y2;
	    }
	    polycurve.curveTo(ccx1, ccy1, ccx2, ccy2, x2, y2);
	    prevx = curx;
	    prevy = cury;
	  }
	}
	curve = polycurve;
      }
    }
  }

  @Override
  public String toString() {
    return super.toString() + "\nctr1=(" + (float) ctr1.getX() + ","
	+ (float) ctr1.getY() + ") ctr2=(" + (float) ctr2.getX() + ","
	+ (float) ctr2.getY() + ")";
  }

  protected Point2D ctr1, ctr2;

  protected Shape curve;

  protected static final boolean debug = false;

}
